#region Copyright(C) 2010-2010 NCS Pte. Ltd. All rights reserved.
// ==================================================================================================
// Copyright(C) 2010 NCS Pte. Ltd. All rights reserved.
//
// SYSTEM NAME	    : iConnect .Net Application Framework
// COMPONENT ID	    : CBEC.Core.Wcf
// COMPONENT DESC   : Core components for WCF.	
//
// CREATED DATE/BY  : 18 Aug 2010 / Gao Ye
//
// REVISION HISTORY :	
// DATE/BY			SR#/CS/PM#/OTHERS		DESCRIPTION OF CHANGE
// ==================================================================================================
#endregion

using CBEC.Core.Wcf.Properties;
using System;
using System.Reflection;
using System.ServiceModel;

namespace CBEC.Core.Wcf
{
    /// <summary>
    /// Helps to extract the inner exception in generic FaultException. It can work well with exception handling application block.
    /// </summary>
    internal static class FaultExceptionHelper
    {
        /// <summary>
        /// Extracts the exception, if the exception is generic faultException.
        /// </summary>
        /// <param name="exception">The exception thrown by system.</param>
        /// <param name="handingInstanceId">The handing instance id.</param>
        /// <returns>
        /// If the exception is <see ref="FaultException" />&lt;<see ref="ServiceException" />&gt;
        /// </returns>
        /// <example>
        ///   <code>
        /// try
        /// {
        /// ...
        /// }
        /// catch(Exception ex)
        /// {
        /// if (ExceptionPolicy.HandleException(FaultExceptionHelper.Handle(ex), "UIPolicy"))
        /// {
        /// throw;
        /// }
        /// }
        ///   </code>
        ///   </example>
        public static Exception Handle(Exception exception, out Guid handingInstanceId)
        {
            handingInstanceId = Guid.Empty;
            FaultException<ServiceExceptionDetail> serviceExceptionFault = exception as FaultException<ServiceExceptionDetail>;
            if (serviceExceptionFault != null)
            {
                if (serviceExceptionFault.Detail == null)
                {
                    return exception;
                }
                handingInstanceId = serviceExceptionFault.Detail.HandingInstanceId;
                Exception actualException = ExtractException(serviceExceptionFault.Detail);
                if (actualException == null)
                {
                    return exception;
                }
                return actualException;
            }

            FaultException<ValidationFault> validationFault = exception as FaultException<ValidationFault>;
            if (null != validationFault)
            {
                ValidationException validationException = new ValidationException(Resources.ValidationExceptionMessage);
                foreach (ValidationError error in validationFault.Detail.Errors)
                {
                    validationException.Errors.Add(error);
                }
                throw validationException;
            }

            return exception;
        }    

        /// <summary>
        /// Extracts the exception.
        /// </summary>
        /// <param name="detail">The detail.</param>
        /// <returns></returns>
        private static Exception ExtractException(ServiceExceptionDetail detail)
        {
            Exception innerException = null;
            if (detail.InnerException != null)
            {
                innerException = ExtractException(detail.InnerException);
            }

            //AppDomain.CurrentDomain.
            Type type = Type.GetType(detail.TypeAssemblyQualifiedName);

            return CreateExceptionInstance(type, detail, innerException);
        }

        /// <summary>
        /// Creates the exception instance.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <param name="detail">The detail.</param>
        /// <param name="innerException">The inner exception.</param>
        /// <returns></returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2201:DoNotRaiseReservedExceptionTypes"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        private static Exception CreateExceptionInstance(Type type, ServiceExceptionDetail detail, Exception innerException)
        {
            //* Using "Exception exception = Activator.CreateInstance(type) as Exception;"
            //* not "Exception exception = Activator.CreateInstance(type,detail.Message,innerException) as Exception;"
            //* It is because the second code will be failed when the exception type is ArgumentException. The reason is:
            //* ArgumentException(string message, Exception innerException);
            //* ArgumentException(string message, string paramName, Exception innerException); 
            //* system will not know use which constructor to create.
            Exception exception = null;
            if (type != null)
            {
                try
                {
                    exception = Activator.CreateInstance(type, true) as Exception;

                    if (exception == null)
                    {
                        exception = new Exception();
                    }

                    SetExceptionField(exception, "_message", detail.Message);
                    SetExceptionField(exception, "_innerException", innerException);
                    SetExceptionField(exception, "_stackTraceString", detail.StackTrace);
                    SetExceptionField(exception, "_helpURL", detail.HelpLink);
                }
                catch (Exception ex)
                {
                    Exception inner = new Exception();
                    SetExceptionField(inner, "_message", detail.Message);
                    SetExceptionField(inner, "_innerException", innerException);
                    SetExceptionField(inner, "_stackTraceString", detail.StackTrace);
                    SetExceptionField(inner, "_helpURL", detail.HelpLink);
                    exception = new Exception(ex.Message, inner);
                }
            }

            return exception;
        }

        /// <summary>
        /// Sets the exception field.
        /// </summary>
        /// <param name="exception">The exception.</param>
        /// <param name="field">The field.</param>
        /// <param name="value">The value.</param>
        private static void SetExceptionField(Exception exception, string field, object value)
        {
            FieldInfo fieldInfo = typeof (Exception).GetField(field, BindingFlags.Instance | BindingFlags.NonPublic);
            fieldInfo.SetValue(exception, value);
        }
    }
}